package cn.jiedanba.itext.gm;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CRL;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.Set;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1Enumerated;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1OutputStream;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1TaggedObject;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.esf.SignaturePolicyIdentifier;
import org.bouncycastle.asn1.ess.ESSCertID;
import org.bouncycastle.asn1.ess.ESSCertIDv2;
import org.bouncycastle.asn1.ess.SigningCertificate;
import org.bouncycastle.asn1.ess.SigningCertificateV2;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.ocsp.BasicOCSPResponse;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.tsp.MessageImprint;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.ocsp.BasicOCSPResp;
import org.bouncycastle.cert.ocsp.CertificateID;
import org.bouncycastle.cert.ocsp.SingleResp;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.jce.provider.X509CertParser;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;

import com.itextpdf.text.ExceptionConverter;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.CertificateInfo;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.EncryptionAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.MakeSignature.CryptoStandard;
import com.itextpdf.text.pdf.security.SecurityIDs;
import com.itextpdf.text.pdf.security.TSAClient;

/**
 * 重写
 * 
 * @author dell
 *
 */
public class PdfPKCS7 {

	// Constructors for creating new signatures

	/**
	 * Assembles all the elements needed to create a signature, except for the
	 * data.
	 * 
	 * @param privKey
	 *            the private key
	 * @param certChain
	 *            the certificate chain
	 * @param interfaceDigest
	 *            the interface digest
	 * @param hashAlgorithm
	 *            the hash algorithm
	 * @param provider
	 *            the provider or <code>null</code> for the default provider
	 * @param hasRSAdata
	 *            <CODE>true</CODE> if the sub-filter is adbe.pkcs7.sha1
	 * @throws InvalidKeyException
	 *             on error
	 * @throws NoSuchProviderException
	 *             on error
	 * @throws NoSuchAlgorithmException
	 *             on error
	 */
	public PdfPKCS7(PrivateKey privKey, Certificate[] certChain, String hashAlgorithm, String provider,
			ExternalDigest interfaceDigest, boolean hasRSAdata)
			throws InvalidKeyException, NoSuchProviderException, NoSuchAlgorithmException {
		this.provider = provider;
		this.interfaceDigest = interfaceDigest;
		// message digest
		digestAlgorithmOid = DigestAlgorithms.getAllowedDigests(hashAlgorithm);
		if (digestAlgorithmOid == null)
			throw new NoSuchAlgorithmException(
					MessageLocalization.getComposedMessage("unknown.hash.algorithm.1", hashAlgorithm));

		// Copy the certificates
		signCert = (X509Certificate) certChain[0];
		certs = new ArrayList<Certificate>();
		for (Certificate element : certChain) {
			certs.add(element);
		}

		// initialize and add the digest algorithms.
		digestalgos = new HashSet<String>();
		digestalgos.add(digestAlgorithmOid);

		// find the signing algorithm (RSA or DSA)
		if (privKey != null) {
			digestEncryptionAlgorithmOid = privKey.getAlgorithm();
			if (digestEncryptionAlgorithmOid.equals("RSA")) {
				digestEncryptionAlgorithmOid = SecurityIDs.ID_RSA;
			} else if (digestEncryptionAlgorithmOid.equals("DSA")) {
				digestEncryptionAlgorithmOid = SecurityIDs.ID_DSA;
			} else {
				throw new NoSuchAlgorithmException(MessageLocalization.getComposedMessage("unknown.key.algorithm.1",
						digestEncryptionAlgorithmOid));
			}
		}

		// initialize the RSA data
		if (hasRSAdata) {
			RSAdata = new byte[0];
			messageDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), provider);
		}

		// initialize the Signature object
		if (privKey != null) {
			sig = initSignature(privKey);
		}
	}

	// Constructors for validating existing signatures

	/**
	 * Use this constructor if you want to verify a signature using the
	 * sub-filter adbe.x509.rsa_sha1.
	 * 
	 * @param contentsKey
	 *            the /Contents key
	 * @param certsKey
	 *            the /Cert key
	 * @param provider
	 *            the provider or <code>null</code> for the default provider
	 */
	@SuppressWarnings("unchecked")
	public PdfPKCS7(byte[] contentsKey, byte[] certsKey, String provider) {
		try {
			this.provider = provider;
			X509CertParser cr = new X509CertParser();
			cr.engineInit(new ByteArrayInputStream(certsKey));
			certs = cr.engineReadAll();
			signCerts = certs;
			signCert = (X509Certificate) certs.iterator().next();
			crls = new ArrayList<CRL>();

			ASN1InputStream in = new ASN1InputStream(new ByteArrayInputStream(contentsKey));
			digest = ((ASN1OctetString) in.readObject()).getOctets();

			if (provider == null) {
				sig = Signature.getInstance("SHA1withRSA");
			} else {
				sig = Signature.getInstance("SHA1withRSA", provider);
			}

			sig.initVerify(signCert.getPublicKey());

			// setting the oid to SHA1withRSA
			digestAlgorithmOid = "1.2.840.10040.4.3";
			digestEncryptionAlgorithmOid = "1.3.36.3.3.1.2";
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	/**
	 * Use this constructor if you want to verify a signature.
	 * 
	 * @param contentsKey
	 *            the /Contents key
	 * @param filterSubtype
	 *            the filtersubtype
	 * @param provider
	 *            the provider or <code>null</code> for the default provider
	 */
	@SuppressWarnings({ "unchecked" })
	public PdfPKCS7(byte[] contentsKey, PdfName filterSubtype, String provider) {
		this.filterSubtype = filterSubtype;
		isTsp = PdfName.ETSI_RFC3161.equals(filterSubtype);
		isCades = PdfName.ETSI_CADES_DETACHED.equals(filterSubtype);
		try {
			this.provider = provider;
			ASN1InputStream din = new ASN1InputStream(new ByteArrayInputStream(contentsKey));

			//
			// Basic checks to make sure it's a PKCS#7 SignedData Object
			//
			ASN1Primitive pkcs;

			try {
				pkcs = din.readObject();
			} catch (IOException e) {
				throw new IllegalArgumentException(
						MessageLocalization.getComposedMessage("can.t.decode.pkcs7signeddata.object"));
			}
			if (!(pkcs instanceof ASN1Sequence)) {
				throw new IllegalArgumentException(
						MessageLocalization.getComposedMessage("not.a.valid.pkcs.7.object.not.a.sequence"));
			}
			ASN1Sequence signedData = (ASN1Sequence) pkcs;
			ASN1ObjectIdentifier objId = (ASN1ObjectIdentifier) signedData.getObjectAt(0);
			if (!objId.getId().equals(SecurityIDs.ID_PKCS7_SIGNED_DATA))
				throw new IllegalArgumentException(
						MessageLocalization.getComposedMessage("not.a.valid.pkcs.7.object.not.signed.data"));
			ASN1Sequence content = (ASN1Sequence) ((ASN1TaggedObject) signedData.getObjectAt(1)).getObject();
			// the positions that we care are:
			// 0 - version
			// 1 - digestAlgorithms
			// 2 - possible ID_PKCS7_DATA
			// (the certificates and crls are taken out by other means)
			// last - signerInfos

			// the version
			version = ((ASN1Integer) content.getObjectAt(0)).getValue().intValue();

			// the digestAlgorithms
			digestalgos = new HashSet<String>();
			Enumeration<ASN1Sequence> e = ((ASN1Set) content.getObjectAt(1)).getObjects();
			while (e.hasMoreElements()) {
				ASN1Sequence s = e.nextElement();
				ASN1ObjectIdentifier o = (ASN1ObjectIdentifier) s.getObjectAt(0);
				digestalgos.add(o.getId());
			}

			// the possible ID_PKCS7_DATA
			ASN1Sequence rsaData = (ASN1Sequence) content.getObjectAt(2);
			if (rsaData.size() > 1) {
				ASN1OctetString rsaDataContent = (ASN1OctetString) ((ASN1TaggedObject) rsaData.getObjectAt(1))
						.getObject();
				RSAdata = rsaDataContent.getOctets();
			}

			int next = 3;
			while (content.getObjectAt(next) instanceof ASN1TaggedObject)
				++next;

			// the certificates
			/*
			 * This should work, but that's not always the case because of a bug
			 * in BouncyCastle:
			 */
			X509CertParser cr = new X509CertParser();
			cr.engineInit(new ByteArrayInputStream(contentsKey));
			certs = cr.engineReadAll();
			/*
			 * The following workaround was provided by Alfonso Massa, but it
			 * doesn't always work either.
			 * 
			 * ASN1Set certSet = null; ASN1Set crlSet = null; while
			 * (content.getObjectAt(next) instanceof ASN1TaggedObject) {
			 * ASN1TaggedObject tagged =
			 * (ASN1TaggedObject)content.getObjectAt(next);
			 * 
			 * switch (tagged.getTagNo()) { case 0: certSet =
			 * ASN1Set.getInstance(tagged, false); break; case 1: crlSet =
			 * ASN1Set.getInstance(tagged, false); break; default: throw new
			 * IllegalArgumentException("unknown tag value " +
			 * tagged.getTagNo()); } ++next; } certs = new
			 * ArrayList<Certificate>(certSet.size());
			 * 
			 * CertificateFactory certFact =
			 * CertificateFactory.getInstance("X.509", new
			 * BouncyCastleProvider()); for (Enumeration en =
			 * certSet.getObjects(); en.hasMoreElements();) { ASN1Primitive obj
			 * = ((ASN1Encodable)en.nextElement()).toASN1Primitive(); if (obj
			 * instanceof ASN1Sequence) { ByteArrayInputStream stream = new
			 * ByteArrayInputStream(obj.getEncoded()); X509Certificate
			 * x509Certificate =
			 * (X509Certificate)certFact.generateCertificate(stream);
			 * stream.close(); certs.add(x509Certificate); } }
			 */
			// the signerInfos
			ASN1Set signerInfos = (ASN1Set) content.getObjectAt(next);
			if (signerInfos.size() != 1)
				throw new IllegalArgumentException(MessageLocalization.getComposedMessage(
						"this.pkcs.7.object.has.multiple.signerinfos.only.one.is.supported.at.this.time"));
			ASN1Sequence signerInfo = (ASN1Sequence) signerInfos.getObjectAt(0);
			// the positions that we care are
			// 0 - version
			// 1 - the signing certificate issuer and serial number
			// 2 - the digest algorithm
			// 3 or 4 - digestEncryptionAlgorithm
			// 4 or 5 - encryptedDigest
			signerversion = ((ASN1Integer) signerInfo.getObjectAt(0)).getValue().intValue();
			// Get the signing certificate
			ASN1Sequence issuerAndSerialNumber = (ASN1Sequence) signerInfo.getObjectAt(1);
			X509Principal issuer = new X509Principal(
					issuerAndSerialNumber.getObjectAt(0).toASN1Primitive().getEncoded());
			BigInteger serialNumber = ((ASN1Integer) issuerAndSerialNumber.getObjectAt(1)).getValue();
			for (Object element : certs) {
				X509Certificate cert = (X509Certificate) element;
				if (cert.getIssuerDN().equals(issuer) && serialNumber.equals(cert.getSerialNumber())) {
					signCert = cert;
					break;
				}
			}
			if (signCert == null) {
				throw new IllegalArgumentException(
						MessageLocalization.getComposedMessage("can.t.find.signing.certificate.with.serial.1",
								issuer.getName() + " / " + serialNumber.toString(16)));
			}
			signCertificateChain();
			digestAlgorithmOid = ((ASN1ObjectIdentifier) ((ASN1Sequence) signerInfo.getObjectAt(2)).getObjectAt(0))
					.getId();
			next = 3;
			boolean foundCades = false;
			if (signerInfo.getObjectAt(next) instanceof ASN1TaggedObject) {
				ASN1TaggedObject tagsig = (ASN1TaggedObject) signerInfo.getObjectAt(next);
				ASN1Set sseq = ASN1Set.getInstance(tagsig, false);
				sigAttr = sseq.getEncoded();
				// maybe not necessary, but we use the following line as
				// fallback:
				sigAttrDer = sseq.getEncoded(ASN1Encoding.DER);

				for (int k = 0; k < sseq.size(); ++k) {
					ASN1Sequence seq2 = (ASN1Sequence) sseq.getObjectAt(k);
					String idSeq2 = ((ASN1ObjectIdentifier) seq2.getObjectAt(0)).getId();
					if (idSeq2.equals(SecurityIDs.ID_MESSAGE_DIGEST)) {
						ASN1Set set = (ASN1Set) seq2.getObjectAt(1);
						digestAttr = ((ASN1OctetString) set.getObjectAt(0)).getOctets();
					} else if (idSeq2.equals(SecurityIDs.ID_ADBE_REVOCATION)) {
						ASN1Set setout = (ASN1Set) seq2.getObjectAt(1);
						ASN1Sequence seqout = (ASN1Sequence) setout.getObjectAt(0);
						for (int j = 0; j < seqout.size(); ++j) {
							ASN1TaggedObject tg = (ASN1TaggedObject) seqout.getObjectAt(j);
							if (tg.getTagNo() == 0) {
								ASN1Sequence seqin = (ASN1Sequence) tg.getObject();
								findCRL(seqin);
							}
							if (tg.getTagNo() == 1) {
								ASN1Sequence seqin = (ASN1Sequence) tg.getObject();
								findOcsp(seqin);
							}
						}
					} else if (isCades && idSeq2.equals(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V1)) {
						ASN1Set setout = (ASN1Set) seq2.getObjectAt(1);
						ASN1Sequence seqout = (ASN1Sequence) setout.getObjectAt(0);
						SigningCertificate sv2 = SigningCertificate.getInstance(seqout);
						ESSCertID[] cerv2m = sv2.getCerts();
						ESSCertID cerv2 = cerv2m[0];
						byte[] enc2 = signCert.getEncoded();
						MessageDigest m2 = new BouncyCastleDigest().getMessageDigest("SHA-1");
						byte[] signCertHash = m2.digest(enc2);
						byte[] hs2 = cerv2.getCertHash();
						if (!Arrays.equals(signCertHash, hs2))
							throw new IllegalArgumentException(
									"Signing certificate doesn't match the ESS information.");
						foundCades = true;
					} else if (isCades && idSeq2.equals(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V2)) {
						ASN1Set setout = (ASN1Set) seq2.getObjectAt(1);
						ASN1Sequence seqout = (ASN1Sequence) setout.getObjectAt(0);
						SigningCertificateV2 sv2 = SigningCertificateV2.getInstance(seqout);
						ESSCertIDv2[] cerv2m = sv2.getCerts();
						ESSCertIDv2 cerv2 = cerv2m[0];
						AlgorithmIdentifier ai2 = cerv2.getHashAlgorithm();
						byte[] enc2 = signCert.getEncoded();
						MessageDigest m2 = new BouncyCastleDigest()
								.getMessageDigest(DigestAlgorithms.getDigest(ai2.getAlgorithm().getId()));
						byte[] signCertHash = m2.digest(enc2);
						byte[] hs2 = cerv2.getCertHash();
						if (!Arrays.equals(signCertHash, hs2))
							throw new IllegalArgumentException(
									"Signing certificate doesn't match the ESS information.");
						foundCades = true;
					}
				}
				if (digestAttr == null)
					throw new IllegalArgumentException(
							MessageLocalization.getComposedMessage("authenticated.attribute.is.missing.the.digest"));
				++next;
			}
			if (isCades && !foundCades)
				throw new IllegalArgumentException("CAdES ESS information missing.");
			digestEncryptionAlgorithmOid = ((ASN1ObjectIdentifier) ((ASN1Sequence) signerInfo.getObjectAt(next++))
					.getObjectAt(0)).getId();
			digest = ((ASN1OctetString) signerInfo.getObjectAt(next++)).getOctets();
			if (next < signerInfo.size() && signerInfo.getObjectAt(next) instanceof ASN1TaggedObject) {
				ASN1TaggedObject taggedObject = (ASN1TaggedObject) signerInfo.getObjectAt(next);
				ASN1Set unat = ASN1Set.getInstance(taggedObject, false);
				AttributeTable attble = new AttributeTable(unat);
				Attribute ts = attble.get(PKCSObjectIdentifiers.id_aa_signatureTimeStampToken);
				if (ts != null && ts.getAttrValues().size() > 0) {
					ASN1Set attributeValues = ts.getAttrValues();
					ASN1Sequence tokenSequence = ASN1Sequence.getInstance(attributeValues.getObjectAt(0));
					ContentInfo contentInfo = new ContentInfo(tokenSequence);
					this.timeStampToken = new TimeStampToken(contentInfo);
				}
			}
			if (isTsp) {
				ContentInfo contentInfoTsp = new ContentInfo(signedData);
				this.timeStampToken = new TimeStampToken(contentInfoTsp);
				TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
				String algOID = info.getMessageImprintAlgOID().getId();
				messageDigest = DigestAlgorithms.getMessageDigestFromOid(algOID, null);
			} else {
				if (RSAdata != null || digestAttr != null) {
					if (PdfName.ADBE_PKCS7_SHA1.equals(getFilterSubtype())) {
						messageDigest = DigestAlgorithms.getMessageDigest("SHA1", provider);
					} else {
						messageDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), provider);
					}
					encContDigest = DigestAlgorithms.getMessageDigest(getHashAlgorithm(), provider);
				}
				sig = initSignature(signCert.getPublicKey());
			}
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	// Encryption provider

	/** The encryption provider, e.g. "BC" if you use BouncyCastle. */
	private String provider;

	// Signature info

	private SignaturePolicyIdentifier signaturePolicyIdentifier;

	/** Holds value of property signName. */
	private String signName;

	/** Holds value of property reason. */
	private String reason;

	/** Holds value of property location. */
	private String location;

	/** Holds value of property signDate. */
	private Calendar signDate;

	public void setSignaturePolicy(SignaturePolicyIdentifier signaturePolicy) {
		this.signaturePolicyIdentifier = signaturePolicy;
	}

	/**
	 * Getter for property sigName.
	 * 
	 * @return Value of property sigName.
	 */
	public String getSignName() {
		return this.signName;
	}

	/**
	 * Setter for property sigName.
	 * 
	 * @param signName
	 *            New value of property sigName.
	 */
	public void setSignName(String signName) {
		this.signName = signName;
	}

	/**
	 * Getter for property reason.
	 * 
	 * @return Value of property reason.
	 */
	public String getReason() {
		return this.reason;
	}

	/**
	 * Setter for property reason.
	 * 
	 * @param reason
	 *            New value of property reason.
	 */
	public void setReason(String reason) {
		this.reason = reason;
	}

	/**
	 * Getter for property location.
	 * 
	 * @return Value of property location.
	 */
	public String getLocation() {
		return this.location;
	}

	/**
	 * Setter for property location.
	 * 
	 * @param location
	 *            New value of property location.
	 */
	public void setLocation(String location) {
		this.location = location;
	}

	/**
	 * Getter for property signDate.
	 * 
	 * @return Value of property signDate.
	 */
	public Calendar getSignDate() {
		Calendar dt = getTimeStampDate();
		if (dt == null)
			return this.signDate;
		else
			return dt;
	}

	/**
	 * Setter for property signDate.
	 * 
	 * @param signDate
	 *            New value of property signDate.
	 */
	public void setSignDate(Calendar signDate) {
		this.signDate = signDate;
	}

	// version info

	/** Version of the PKCS#7 object */
	private int version = 1;

	/** Version of the PKCS#7 "SignerInfo" object. */
	private int signerversion = 1;

	/**
	 * Get the version of the PKCS#7 object.
	 * 
	 * @return the version of the PKCS#7 object.
	 */
	public int getVersion() {
		return version;
	}

	/**
	 * Get the version of the PKCS#7 "SignerInfo" object.
	 * 
	 * @return the version of the PKCS#7 "SignerInfo" object.
	 */
	public int getSigningInfoVersion() {
		return signerversion;
	}

	// Message digest algorithm

	/** The ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1". */
	private String digestAlgorithmOid;

	/** The object that will create the digest */
	private MessageDigest messageDigest;

	/** The digest algorithms */
	private Set<String> digestalgos;

	/** The digest attributes */
	private byte[] digestAttr;

	private PdfName filterSubtype;

	/**
	 * Getter for the ID of the digest algorithm, e.g. "2.16.840.1.101.3.4.2.1"
	 */
	public String getDigestAlgorithmOid() {
		return digestAlgorithmOid;
	}

	/**
	 * Returns the name of the digest algorithm, e.g. "SHA256".
	 * 
	 * @return the digest algorithm name, e.g. "SHA256"
	 */
	public String getHashAlgorithm() {
		return DigestAlgorithms.getDigest(digestAlgorithmOid);
	}

	// Encryption algorithm

	/** The encryption algorithm. */
	private String digestEncryptionAlgorithmOid;

	/**
	 * Getter for the digest encryption algorithm
	 */
	public String getDigestEncryptionAlgorithmOid() {
		return digestEncryptionAlgorithmOid;
	}

	/**
	 * Get the algorithm used to calculate the message digest, e.g.
	 * "SHA1withRSA".
	 * 
	 * @return the algorithm used to calculate the message digest
	 */
	public String getDigestAlgorithm() {
		return getHashAlgorithm() + "with" + getEncryptionAlgorithm();
	}

	/*
	 * DIGITAL SIGNATURE CREATION
	 */

	private ExternalDigest interfaceDigest;
	// The signature is created externally

	/** The signed digest if created outside this class */
	private byte externalDigest[];

	/** External RSA data */
	private byte externalRSAdata[];

	// The signature is created internally

	/**
	 * Class from the Java SDK that provides the functionality of a digital
	 * signature algorithm.
	 */
	private Signature sig;

	/**
	 * The signed digest as calculated by this class (or extracted from an
	 * existing PDF)
	 */
	private byte[] digest;

	/** The RSA data */
	private byte[] RSAdata;

	// Signing functionality.

	private Signature initSignature(PrivateKey key)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
		Signature signature;
		if (provider == null)
			signature = Signature.getInstance(getDigestAlgorithm());
		else
			signature = Signature.getInstance(getDigestAlgorithm(), provider);
		signature.initSign(key);
		return signature;
	}

	private Signature initSignature(PublicKey key)
			throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException {
		String digestAlgorithm = getDigestAlgorithm();
		if (PdfName.ADBE_X509_RSA_SHA1.equals(getFilterSubtype()))
			digestAlgorithm = "SHA1withRSA";
		Signature signature;
		if (provider == null)
			signature = Signature.getInstance(digestAlgorithm);
		else
			signature = Signature.getInstance(digestAlgorithm, provider);

		signature.initVerify(key);
		return signature;
	}

	/**
	 * Update the digest with the specified bytes. This method is used both for
	 * signing and verifying
	 * 
	 * @param buf
	 *            the data buffer
	 * @param off
	 *            the offset in the data buffer
	 * @param len
	 *            the data length
	 * @throws SignatureException
	 *             on error
	 */
	public void update(byte[] buf, int off, int len) throws SignatureException {
		if (RSAdata != null || digestAttr != null || isTsp)
			messageDigest.update(buf, off, len);
		else
			sig.update(buf, off, len);
	}

	// adbe.x509.rsa_sha1 (PKCS#1)

	/**
	 * Gets the bytes for the PKCS#1 object.
	 * 
	 * @return a byte array
	 */
	public byte[] getEncodedPKCS1() {
		try {
			if (externalDigest != null)
				digest = externalDigest;
			else
				digest = sig.sign();
			ByteArrayOutputStream bOut = new ByteArrayOutputStream();

			ASN1OutputStream dout = new ASN1OutputStream(bOut);
			dout.writeObject(new DEROctetString(digest));
			dout.close();

			return bOut.toByteArray();
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	// other subfilters (PKCS#7)

	/**
	 * Gets the bytes for the PKCS7SignedData object.
	 * 
	 * @return the bytes for the PKCS7SignedData object
	 */
	public byte[] getEncodedPKCS7() {
		return getEncodedPKCS7(null, null, null, null, CryptoStandard.CMS);
	}

	/**
	 * Gets the bytes for the PKCS7SignedData object. Optionally the
	 * authenticatedAttributes in the signerInfo can also be set. If either of
	 * the parameters is <CODE>null</CODE>, none will be used.
	 * 
	 * @param secondDigest
	 *            the digest in the authenticatedAttributes
	 * @return the bytes for the PKCS7SignedData object
	 */
	public byte[] getEncodedPKCS7(byte secondDigest[]) {
		return getEncodedPKCS7(secondDigest, null, null, null, CryptoStandard.CMS);
	}

	/**
	 * Gets the bytes for the PKCS7SignedData object. Optionally the
	 * authenticatedAttributes in the signerInfo can also be set, OR a
	 * time-stamp-authority client may be provided.
	 * 
	 * @param secondDigest
	 *            the digest in the authenticatedAttributes
	 * @param tsaClient
	 *            TSAClient - null or an optional time stamp authority client
	 * @return byte[] the bytes for the PKCS7SignedData object
	 * @since 2.1.6
	 */
	public byte[] getEncodedPKCS7(byte secondDigest[], TSAClient tsaClient, byte[] ocsp, Collection<byte[]> crlBytes,
			CryptoStandard sigtype) {
		try {
			if (externalDigest != null) {
				digest = externalDigest;
				if (RSAdata != null)
					RSAdata = externalRSAdata;
			} else if (externalRSAdata != null && RSAdata != null) {
				RSAdata = externalRSAdata;
				sig.update(RSAdata);
				digest = sig.sign();
			} else {
				if (RSAdata != null) {
					RSAdata = messageDigest.digest();
					sig.update(RSAdata);
				}
				digest = sig.sign();
			}

			// Create the set of Hash algorithms
			ASN1EncodableVector digestAlgorithms = new ASN1EncodableVector();
			for (Object element : digestalgos) {
				ASN1EncodableVector algos = new ASN1EncodableVector();
				algos.add(new ASN1ObjectIdentifier((String) element));
				algos.add(DERNull.INSTANCE);
				digestAlgorithms.add(new DERSequence(algos));
			}

			// Create the contentInfo.
			ASN1EncodableVector v = new ASN1EncodableVector();
			v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA));
			if (RSAdata != null)
				v.add(new DERTaggedObject(0, new DEROctetString(RSAdata)));
			DERSequence contentinfo = new DERSequence(v);

			// Get all the certificates
			//
			v = new ASN1EncodableVector();
			for (Object element : certs) {
				ASN1InputStream tempstream = new ASN1InputStream(
						new ByteArrayInputStream(((X509Certificate) element).getEncoded()));
				v.add(tempstream.readObject());
			}

			DERSet dercertificates = new DERSet(v);

			// Create signerinfo structure.
			//
			ASN1EncodableVector signerinfo = new ASN1EncodableVector();

			// Add the signerInfo version
			//
			signerinfo.add(new ASN1Integer(signerversion));

			v = new ASN1EncodableVector();
			v.add(CertificateInfo.getIssuer(signCert.getTBSCertificate()));
			v.add(new ASN1Integer(signCert.getSerialNumber()));
			signerinfo.add(new DERSequence(v));

			// Add the digestAlgorithm
			v = new ASN1EncodableVector();
			v.add(new ASN1ObjectIdentifier(digestAlgorithmOid));
			v.add(new DERNull());
			signerinfo.add(new DERSequence(v));

			// add the authenticated attribute if present
			if (secondDigest != null) {
				signerinfo.add(new DERTaggedObject(false, 0,
						getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype)));
			}
			// Add the digestEncryptionAlgorithm
			v = new ASN1EncodableVector();
			v.add(new ASN1ObjectIdentifier(digestEncryptionAlgorithmOid));
			v.add(new DERNull());
			signerinfo.add(new DERSequence(v));

			// Add the digest
			signerinfo.add(new DEROctetString(digest));

			// When requested, go get and add the timestamp. May throw an
			// exception.
			// Added by Martin Brunecky, 07/12/2007 folowing Aiken Sam,
			// 2006-11-15
			// Sam found Adobe expects time-stamped SHA1-1 of the encrypted
			// digest
			if (tsaClient != null) {
				byte[] tsImprint = tsaClient.getMessageDigest().digest(digest);
				byte[] tsToken = tsaClient.getTimeStampToken(tsImprint);
				if (tsToken != null) {
					ASN1EncodableVector unauthAttributes = buildUnauthenticatedAttributes(tsToken);
					if (unauthAttributes != null) {
						signerinfo.add(new DERTaggedObject(false, 1, new DERSet(unauthAttributes)));
					}
				}
			}

			// Finally build the body out of all the components above
			ASN1EncodableVector body = new ASN1EncodableVector();
			body.add(new ASN1Integer(version));
			body.add(new DERSet(digestAlgorithms));
			body.add(contentinfo);
			body.add(new DERTaggedObject(false, 0, dercertificates));

			// Only allow one signerInfo
			body.add(new DERSet(new DERSequence(signerinfo)));

			// Now we have the body, wrap it in it's PKCS7Signed shell
			// and return it
			//
			ASN1EncodableVector whole = new ASN1EncodableVector();
			whole.add(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_SIGNED_DATA));
			whole.add(new DERTaggedObject(0, new DERSequence(body)));

			ByteArrayOutputStream bOut = new ByteArrayOutputStream();

			ASN1OutputStream dout = new ASN1OutputStream(bOut);
			dout.writeObject(new DERSequence(whole));
			dout.close();

			return bOut.toByteArray();
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	/**
	 * Added by Aiken Sam, 2006-11-15, modifed by Martin Brunecky 07/12/2007 to
	 * start with the timeStampToken (signedData 1.2.840.113549.1.7.2). Token is
	 * the TSA response without response status, which is usually handled by the
	 * (vendor supplied) TSA request/response interface).
	 * 
	 * @param timeStampToken
	 *            byte[] - time stamp token, DER encoded signedData
	 * @return ASN1EncodableVector
	 * @throws IOException
	 */
	private ASN1EncodableVector buildUnauthenticatedAttributes(byte[] timeStampToken) throws IOException {
		if (timeStampToken == null)
			return null;

		// @todo: move this together with the rest of the defintions
		String ID_TIME_STAMP_TOKEN = "1.2.840.113549.1.9.16.2.14"; // RFC 3161
																	// id-aa-timeStampToken

		ASN1InputStream tempstream = new ASN1InputStream(new ByteArrayInputStream(timeStampToken));
		ASN1EncodableVector unauthAttributes = new ASN1EncodableVector();

		ASN1EncodableVector v = new ASN1EncodableVector();
		v.add(new ASN1ObjectIdentifier(ID_TIME_STAMP_TOKEN)); // id-aa-timeStampToken
		ASN1Sequence seq = (ASN1Sequence) tempstream.readObject();
		v.add(new DERSet(seq));

		unauthAttributes.add(new DERSequence(v));
		return unauthAttributes;
	}

	// Authenticated attributes

	/**
	 * When using authenticatedAttributes the authentication process is
	 * different. The document digest is generated and put inside the attribute.
	 * The signing is done over the DER encoded authenticatedAttributes. This
	 * method provides that encoding and the parameters must be exactly the same
	 * as in {@link #getEncodedPKCS7(byte[])}.
	 * <p>
	 * A simple example:
	 * <p>
	 * 
	 * <pre>
	 * Calendar cal = Calendar.getInstance();
	 * PdfPKCS7 pk7 = new PdfPKCS7(key, chain, null, "SHA1", null, false);
	 * MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
	 * byte buf[] = new byte[8192];
	 * int n;
	 * InputStream inp = sap.getRangeStream();
	 * while ((n = inp.read(buf)) &gt; 0) {
	 * 	messageDigest.update(buf, 0, n);
	 * }
	 * byte hash[] = messageDigest.digest();
	 * byte sh[] = pk7.getAuthenticatedAttributeBytes(hash, cal);
	 * pk7.update(sh, 0, sh.length);
	 * byte sg[] = pk7.getEncodedPKCS7(hash, cal);
	 * </pre>
	 * 
	 * @param secondDigest
	 *            the content digest
	 * @return the byte array representation of the authenticatedAttributes
	 *         ready to be signed
	 */
	public byte[] getAuthenticatedAttributeBytes(byte secondDigest[], byte[] ocsp, Collection<byte[]> crlBytes,
			CryptoStandard sigtype) {
		try {
			return getAuthenticatedAttributeSet(secondDigest, ocsp, crlBytes, sigtype).getEncoded(ASN1Encoding.DER);
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	/**
	 * This method provides that encoding and the parameters must be exactly the
	 * same as in {@link #getEncodedPKCS7(byte[])}.
	 *
	 * @param secondDigest
	 *            the content digest
	 * @return the byte array representation of the authenticatedAttributes
	 *         ready to be signed
	 */
	private DERSet getAuthenticatedAttributeSet(byte secondDigest[], byte[] ocsp, Collection<byte[]> crlBytes,
			CryptoStandard sigtype) {
		try {
			ASN1EncodableVector attribute = new ASN1EncodableVector();
			ASN1EncodableVector v = new ASN1EncodableVector();
			v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_CONTENT_TYPE));
			v.add(new DERSet(new ASN1ObjectIdentifier(SecurityIDs.ID_PKCS7_DATA)));
			attribute.add(new DERSequence(v));
			v = new ASN1EncodableVector();
			v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_MESSAGE_DIGEST));
			v.add(new DERSet(new DEROctetString(secondDigest)));
			attribute.add(new DERSequence(v));
			boolean haveCrl = false;
			if (crlBytes != null) {
				for (byte[] bCrl : crlBytes) {
					if (bCrl != null) {
						haveCrl = true;
						break;
					}
				}
			}
			if (ocsp != null || haveCrl) {
				v = new ASN1EncodableVector();
				v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_ADBE_REVOCATION));

				ASN1EncodableVector revocationV = new ASN1EncodableVector();

				if (haveCrl) {
					ASN1EncodableVector v2 = new ASN1EncodableVector();
					for (byte[] bCrl : crlBytes) {
						if (bCrl == null)
							continue;
						ASN1InputStream t = new ASN1InputStream(new ByteArrayInputStream(bCrl));
						v2.add(t.readObject());
					}
					revocationV.add(new DERTaggedObject(true, 0, new DERSequence(v2)));
				}

				if (ocsp != null) {
					DEROctetString doctet = new DEROctetString(ocsp);
					ASN1EncodableVector vo1 = new ASN1EncodableVector();
					ASN1EncodableVector v2 = new ASN1EncodableVector();
					v2.add(OCSPObjectIdentifiers.id_pkix_ocsp_basic);
					v2.add(doctet);
					ASN1Enumerated den = new ASN1Enumerated(0);
					ASN1EncodableVector v3 = new ASN1EncodableVector();
					v3.add(den);
					v3.add(new DERTaggedObject(true, 0, new DERSequence(v2)));
					vo1.add(new DERSequence(v3));
					revocationV.add(new DERTaggedObject(true, 1, new DERSequence(vo1)));
				}

				v.add(new DERSet(new DERSequence(revocationV)));
				attribute.add(new DERSequence(v));
			}
			if (sigtype == CryptoStandard.CADES) {
				v = new ASN1EncodableVector();
				v.add(new ASN1ObjectIdentifier(SecurityIDs.ID_AA_SIGNING_CERTIFICATE_V2));

				ASN1EncodableVector aaV2 = new ASN1EncodableVector();
				String sha256Oid = DigestAlgorithms.getAllowedDigests(DigestAlgorithms.SHA256);

				// If we look into X.690-0207, clause 11.5, we can see that
				// using DER all the components of a sequence having
				// default values shall not be included. According to RFC 5035,
				// 5.4.1.1, definition of ESSCertIDv2, default
				// AlgorithmIdentifier is sha256.
				if (!sha256Oid.equals(digestAlgorithmOid)) {
					AlgorithmIdentifier algoId = new AlgorithmIdentifier(new ASN1ObjectIdentifier(digestAlgorithmOid));
					aaV2.add(algoId);
				}

				MessageDigest md = interfaceDigest.getMessageDigest(getHashAlgorithm());
				byte[] dig = md.digest(signCert.getEncoded());
				aaV2.add(new DEROctetString(dig));

				v.add(new DERSet(new DERSequence(new DERSequence(new DERSequence(aaV2)))));
				attribute.add(new DERSequence(v));
			}

			if (signaturePolicyIdentifier != null) {
				attribute.add(new Attribute(PKCSObjectIdentifiers.id_aa_ets_sigPolicyId,
						new DERSet(signaturePolicyIdentifier)));
			}

			return new DERSet(attribute);
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	/*
	 * DIGITAL SIGNATURE VERIFICATION
	 */

	/** Signature attributes */
	private byte[] sigAttr;
	/** Signature attributes (maybe not necessary, but we use it as fallback) */
	private byte[] sigAttrDer;

	/** encrypted digest */
	private MessageDigest encContDigest; // Stefan Santesson

	/** Indicates if a signature has already been verified */
	private boolean verified;

	/** The result of the verification */
	private boolean verifyResult;

	// verification

	/**
	 * Verify the digest.
	 * 
	 * @throws SignatureException
	 *             on error
	 * @return <CODE>true</CODE> if the signature checks out, <CODE>false</CODE>
	 *         otherwise
	 * @throws java.security.GeneralSecurityException
	 */
	public boolean verify() throws GeneralSecurityException {
		if (verified)
			return verifyResult;
		if (isTsp) {
			TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
			MessageImprint imprint = info.toASN1Structure().getMessageImprint();
			byte[] md = messageDigest.digest();
			byte[] imphashed = imprint.getHashedMessage();
			verifyResult = Arrays.equals(md, imphashed);
		} else {
			if (sigAttr != null || sigAttrDer != null) {
				final byte[] msgDigestBytes = messageDigest.digest();
				boolean verifyRSAdata = true;
				// Stefan Santesson fixed a bug, keeping the code backward
				// compatible
				boolean encContDigestCompare = false;
				if (RSAdata != null) {
					verifyRSAdata = Arrays.equals(msgDigestBytes, RSAdata);
					encContDigest.update(RSAdata);
					encContDigestCompare = Arrays.equals(encContDigest.digest(), digestAttr);
				}
				boolean absentEncContDigestCompare = Arrays.equals(msgDigestBytes, digestAttr);
				boolean concludingDigestCompare = absentEncContDigestCompare || encContDigestCompare;
				boolean sigVerify = verifySigAttributes(sigAttr) || verifySigAttributes(sigAttrDer);
				verifyResult = concludingDigestCompare && sigVerify && verifyRSAdata;
			} else {
				if (RSAdata != null)
					sig.update(messageDigest.digest());
				verifyResult = sig.verify(digest);
			}
		}
		verified = true;
		return verifyResult;
	}

	private boolean verifySigAttributes(byte[] attr) throws GeneralSecurityException {
		Signature signature = initSignature(signCert.getPublicKey());
		signature.update(attr);
		return signature.verify(digest);
	}

	/**
	 * Checks if the timestamp refers to this document.
	 * 
	 * @return true if it checks false otherwise
	 * @throws GeneralSecurityException
	 *             on error
	 * @since 2.1.6
	 */
	public boolean verifyTimestampImprint() throws GeneralSecurityException {
		if (timeStampToken == null)
			return false;
		TimeStampTokenInfo info = timeStampToken.getTimeStampInfo();
		MessageImprint imprint = info.toASN1Structure().getMessageImprint();
		String algOID = info.getMessageImprintAlgOID().getId();
		byte[] md = new BouncyCastleDigest().getMessageDigest(DigestAlgorithms.getDigest(algOID)).digest(digest);
		byte[] imphashed = imprint.getHashedMessage();
		boolean res = Arrays.equals(md, imphashed);
		return res;
	}

	// Certificates

	/** All the X.509 certificates in no particular order. */
	private Collection<Certificate> certs;

	/** All the X.509 certificates used for the main signature. */
	private Collection<Certificate> signCerts;

	/** The X.509 certificate that is used to sign the digest. */
	private X509Certificate signCert;

	/**
	 * Get all the X.509 certificates associated with this PKCS#7 object in no
	 * particular order. Other certificates, from OCSP for example, will also be
	 * included.
	 * 
	 * @return the X.509 certificates associated with this PKCS#7 object
	 */
	public Certificate[] getCertificates() {
		return certs.toArray(new X509Certificate[certs.size()]);
	}

	/**
	 * Get the X.509 sign certificate chain associated with this PKCS#7 object.
	 * Only the certificates used for the main signature will be returned, with
	 * the signing certificate first.
	 * 
	 * @return the X.509 certificates associated with this PKCS#7 object
	 * @since 2.1.6
	 */
	public Certificate[] getSignCertificateChain() {
		return signCerts.toArray(new X509Certificate[signCerts.size()]);
	}

	/**
	 * Get the X.509 certificate actually used to sign the digest.
	 * 
	 * @return the X.509 certificate actually used to sign the digest
	 */
	public X509Certificate getSigningCertificate() {
		return signCert;
	}

	/**
	 * Helper method that creates the collection of certificates used for the
	 * main signature based on the complete list of certificates and the sign
	 * certificate.
	 */
	private void signCertificateChain() {
		ArrayList<Certificate> cc = new ArrayList<Certificate>();
		cc.add(signCert);
		ArrayList<Certificate> oc = new ArrayList<Certificate>(certs);
		for (int k = 0; k < oc.size(); ++k) {
			if (signCert.equals(oc.get(k))) {
				oc.remove(k);
				--k;
				continue;
			}
		}
		boolean found = true;
		while (found) {
			X509Certificate v = (X509Certificate) cc.get(cc.size() - 1);
			found = false;
			for (int k = 0; k < oc.size(); ++k) {
				X509Certificate issuer = (X509Certificate) oc.get(k);
				try {
					if (provider == null)
						v.verify(issuer.getPublicKey());
					else
						v.verify(issuer.getPublicKey(), provider);
					found = true;
					cc.add(oc.get(k));
					oc.remove(k);
					break;
				} catch (Exception e) {
				}
			}
		}
		signCerts = cc;
	}

	// Certificate Revocation Lists

	private Collection<CRL> crls;

	/**
	 * Get the X.509 certificate revocation lists associated with this PKCS#7
	 * object
	 * 
	 * @return the X.509 certificate revocation lists associated with this
	 *         PKCS#7 object
	 */
	public Collection<CRL> getCRLs() {
		return crls;
	}

	/**
	 * Helper method that tries to construct the CRLs.
	 */
	private void findCRL(ASN1Sequence seq) {
		try {
			crls = new ArrayList<CRL>();
			for (int k = 0; k < seq.size(); ++k) {
				ByteArrayInputStream ar = new ByteArrayInputStream(
						seq.getObjectAt(k).toASN1Primitive().getEncoded(ASN1Encoding.DER));
				CertificateFactory cf = CertificateFactory.getInstance("X.509");
				X509CRL crl = (X509CRL) cf.generateCRL(ar);
				crls.add(crl);
			}
		} catch (Exception ex) {
			// ignore
		}
	}

	// Online Certificate Status Protocol

	/** BouncyCastle BasicOCSPResp */
	private BasicOCSPResp basicResp;

	/**
	 * Gets the OCSP basic response if there is one.
	 * 
	 * @return the OCSP basic response or null
	 * @since 2.1.6
	 */
	public BasicOCSPResp getOcsp() {
		return basicResp;
	}

	/**
	 * Checks if OCSP revocation refers to the document signing certificate.
	 * 
	 * @return true if it checks, false otherwise
	 * @since 2.1.6
	 */
	public boolean isRevocationValid() {
		if (basicResp == null)
			return false;
		if (signCerts.size() < 2)
			return false;
		try {
			X509Certificate[] cs = (X509Certificate[]) getSignCertificateChain();
			SingleResp sr = basicResp.getResponses()[0];
			CertificateID cid = sr.getCertID();
			DigestCalculator digestalg = new JcaDigestCalculatorProviderBuilder().build()
					.get(new AlgorithmIdentifier(cid.getHashAlgOID(), DERNull.INSTANCE));
			X509Certificate sigcer = getSigningCertificate();
			X509Certificate isscer = cs[1];
			CertificateID tis = new CertificateID(digestalg, new JcaX509CertificateHolder(isscer),
					sigcer.getSerialNumber());
			return tis.equals(cid);
		} catch (Exception ex) {
		}
		return false;
	}

	/**
	 * Helper method that creates the BasicOCSPResp object.
	 * 
	 * @param seq
	 * @throws IOException
	 */
	private void findOcsp(ASN1Sequence seq) throws IOException {
		basicResp = null;
		boolean ret = false;
		while (true) {
			if (seq.getObjectAt(0) instanceof ASN1ObjectIdentifier && ((ASN1ObjectIdentifier) seq.getObjectAt(0))
					.getId().equals(OCSPObjectIdentifiers.id_pkix_ocsp_basic.getId())) {
				break;
			}
			ret = true;
			for (int k = 0; k < seq.size(); ++k) {
				if (seq.getObjectAt(k) instanceof ASN1Sequence) {
					seq = (ASN1Sequence) seq.getObjectAt(0);
					ret = false;
					break;
				}
				if (seq.getObjectAt(k) instanceof ASN1TaggedObject) {
					ASN1TaggedObject tag = (ASN1TaggedObject) seq.getObjectAt(k);
					if (tag.getObject() instanceof ASN1Sequence) {
						seq = (ASN1Sequence) tag.getObject();
						ret = false;
						break;
					} else
						return;
				}
			}
			if (ret)
				return;
		}
		ASN1OctetString os = (ASN1OctetString) seq.getObjectAt(1);
		ASN1InputStream inp = new ASN1InputStream(os.getOctets());
		BasicOCSPResponse resp = BasicOCSPResponse.getInstance(inp.readObject());
		basicResp = new BasicOCSPResp(resp);
	}

	// Time Stamps

	/** True if there's a PAdES LTV time stamp. */
	private boolean isTsp;

	/** True if it's a CAdES signature type. */
	private boolean isCades;

	/** BouncyCastle TimeStampToken. */
	private TimeStampToken timeStampToken;

	/**
	 * Check if it's a PAdES-LTV time stamp.
	 * 
	 * @return true if it's a PAdES-LTV time stamp, false otherwise
	 */
	public boolean isTsp() {
		return isTsp;
	}

	/**
	 * Gets the timestamp token if there is one.
	 * 
	 * @return the timestamp token or null
	 * @since 2.1.6
	 */
	public TimeStampToken getTimeStampToken() {
		return timeStampToken;
	}

	/**
	 * Gets the timestamp date
	 * 
	 * @return a date
	 * @since 2.1.6
	 */
	public Calendar getTimeStampDate() {
		if (timeStampToken == null)
			return null;
		Calendar cal = new GregorianCalendar();
		Date date = timeStampToken.getTimeStampInfo().getGenTime();
		cal.setTime(date);
		return cal;
	}

	/**
	 * Returns the filter subtype.
	 */
	public PdfName getFilterSubtype() {
		return filterSubtype;
	}

	/**
	 * Returns the encryption algorithm
	 * 
	 * @return the name of an encryption algorithm
	 */
	public String getEncryptionAlgorithm() {
		String encryptAlgo = EncryptionAlgorithms.getAlgorithm(digestEncryptionAlgorithmOid);
		if (encryptAlgo == null)
			encryptAlgo = digestEncryptionAlgorithmOid;
		return encryptAlgo;
	}

	/**
	 * Sets the digest/signature to an external calculated value.
	 * 
	 * @param digest
	 *            the digest. This is the actual signature
	 * @param RSAdata
	 *            the extra data that goes into the data tag in PKCS#7
	 * @param digestEncryptionAlgorithm
	 *            the encryption algorithm. It may must be <CODE>null</CODE> if
	 *            the <CODE>digest</CODE> is also <CODE>null</CODE>. If the
	 *            <CODE>digest</CODE> is not <CODE>null</CODE> then it may be
	 *            "RSA" or "DSA"
	 */

	public void setExternalDigest(byte digest[], byte RSAdata[], String digestEncryptionAlgorithm) {
		externalDigest = digest;
		externalRSAdata = RSAdata;
		if (digestEncryptionAlgorithm != null) {
			if (digestEncryptionAlgorithm.equals("RSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_RSA;
			} else if (digestEncryptionAlgorithm.equals("DSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_DSA;
			} else if (digestEncryptionAlgorithm.equals("ECDSA")) {
				this.digestEncryptionAlgorithmOid = SecurityIDs.ID_ECDSA;
			} else if (digestEncryptionAlgorithm.equals("SM2")) {
				this.digestEncryptionAlgorithmOid = GMObjectIdentifiers.sm2sign_with_sm3.getId();
			} else if (digestEncryptionAlgorithm.equals("SM3")) {
				this.digestEncryptionAlgorithmOid = GMObjectIdentifiers.sm3.getId();
			} else
				throw new ExceptionConverter(new NoSuchAlgorithmException(
						MessageLocalization.getComposedMessage("unknown.key.algorithm.1", digestEncryptionAlgorithm)));
		}
	}

}
